package com.example.skytakeoutserver.util

import com.alibaba.fastjson.JSON
import com.alibaba.fastjson.JSONObject
import com.example.skytakeoutserver.config.cpn.property.WechatProperty
import com.wechat.pay.contrib.apache.httpclient.WechatPayHttpClientBuilder
import com.wechat.pay.contrib.apache.httpclient.util.PemUtil
import org.apache.commons.lang3.RandomStringUtils
import org.apache.http.client.methods.HttpPost
import org.apache.http.entity.ContentType
import org.apache.http.entity.StringEntity
import org.apache.http.impl.client.CloseableHttpClient
import org.springframework.http.HttpHeaders
import org.springframework.stereotype.Component
import reactor.core.publisher.Mono
import java.io.File
import java.io.FileInputStream
import java.math.BigDecimal
import java.math.RoundingMode
import java.security.Signature
import java.util.Base64

@Component
class WePayUtil
    (private val wechatProperty: WechatProperty) {
    // 微信支付下单接口地址
    private val JSAPI = "https://api.mch.weixin.qq.com/v3/pay/transations/jsapi"
    // 申请退款接口地址
    // todo:

    private val httpClient: CloseableHttpClient by lazy {
        getClient()
    }

    /**
     * 获取支付client
     */
    private fun getClient(): CloseableHttpClient {
        val merchantPrivateKey = PemUtil.loadPrivateKey(wechatProperty.privateKeyFilePath?.let {
            File(
                it
            )
        }?.let { FileInputStream(it) })
        val x509Certificate = PemUtil.loadCertificate(wechatProperty.wechatPayCertFilePath?.let {
            File(
                it
            )
        }?.let { FileInputStream(it) })
        val wechatPayCertificates = listOf(x509Certificate)

        val builder = WechatPayHttpClientBuilder.create()
            .withMerchant(wechatProperty.mchId, wechatProperty.mchSerialNo, merchantPrivateKey)
            .withWechatPay(wechatPayCertificates)

        return builder.build()
    }

    /**
     * 支付
     */
    fun pay(
        orderNum: String,
        total: BigDecimal,
        description: String,
        openId: String
    ): Mono<JSONObject> {
        return jsapi(
            orderNum,
            total,
            description, openId
        ).flatMap { bodyAsString ->
            val jsonObject: JSONObject = JSON.parseObject(bodyAsString)
            val prepayId = jsonObject.getString("prepay_id") ?: return@flatMap Mono.just(jsonObject)

            val timeStamp = (System.currentTimeMillis() / 1000).toString()
            val nonceStr = RandomStringUtils.randomNumeric(32)
            val list: MutableList<Any> = mutableListOf()
            wechatProperty.appid?.let { list.add(it) }
            list.add(timeStamp)
            list.add(nonceStr)
            list.add("prepay_id=$prepayId")

            val message = list.joinToString("\n").toByteArray()

            val signature: Signature = Signature.getInstance("SHA256withRSA")
            signature.initSign(
                PemUtil
                .loadPrivateKey(wechatProperty.privateKeyFilePath?.let { File(it) }
                    ?.let { FileInputStream(it) })
            )
            signature.update(message)
            val packageSign = Base64.getEncoder()
                .encodeToString(signature.sign())

            val jo = JSONObject()
            jo["timeStamp"] = timeStamp
            jo["nonceStr"] = nonceStr
            jo["package"] = "prepay_id=$prepayId"
            jo["signType"] = "RSA"
            jo["paySign"] = packageSign

            Mono.just(jo)
        }
    }


    private fun jsapi(
        orderNum: String,
        total: BigDecimal,
        description: String,
        openId: String
    ): Mono<String> {
        val jsonObject = JSONObject()
        jsonObject["appid"] = wechatProperty.appid
        jsonObject["mchid"] = wechatProperty.mchId
        jsonObject["description"] = description
        jsonObject["out_trade_no"] = orderNum
        jsonObject["notify_url"] = wechatProperty.notifyUrl

        val amount = JSONObject()
        amount["total"] = total.multiply(
            BigDecimal(100).setScale(
                2,
                RoundingMode.HALF_UP
            )
        ).toInt()
        // 人民币
        amount["currency"] = "CNY"

        val payer = JSONObject()
        payer["openid"] = openId

        jsonObject["amount"] = amount
        jsonObject["payer"] = payer
        val body = jsonObject.toString()
        return post(JSAPI, body)
    }

    /**
     *
     */
    private fun post(url: String, body: String): Mono<String> {
        return Mono.fromCallable {
            // Post 请求
            val httpPost = HttpPost(url)

            httpPost.apply {
                addHeader(
                    HttpHeaders.ACCEPT,
                    ContentType.APPLICATION_JSON.toString()
                )
                addHeader(
                    HttpHeaders.CONTENT_TYPE,
                    ContentType.APPLICATION_JSON.toString()
                )
                addHeader("WechatPay-Serial", wechatProperty.mchSerialNo)
                entity = StringEntity(body, ContentType.APPLICATION_JSON)
            }

            // 返回响应
            val response = httpClient.execute(httpPost)
            response.entity.content.bufferedReader().use { it.readText() }
        }
    }
}